package org.xx.armory.spring5.jdbc;

import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ResultSetExtractor;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static java.lang.Integer.min;

/**
 * 抽象的结果集映射类，提供了一组快捷方法，对结果集中的数据进行格式转化。
 *
 * @param <T>
 *         映射结果实体的类型。
 * @see org.xx.armory.commons.StringConverter
 */
public abstract class EntityMapper<K, T>
        extends JdbcMapper<T> {
    /**
     * 从 {@link ResultSet} 中映射一个实体。
     *
     * @param resultSet
     *         包含待映射数据的 {@link ResultSet} 对象。
     * @return 映射结果。
     * @throws org.springframework.dao.DataAccessException
     *         如果从 {@link ResultSet}对象中读取数据时出错。
     */
    protected abstract T extractEntity(
            ResultSet resultSet
    );

    /**
     * 从 {@link ResultSet} 中映射一个实体的主键。
     *
     * @param resultSet
     *         包含待映射数据的 {@link ResultSet} 对象。
     * @return 映射的主键， {@code null} 表示无法获取主键，该记录应当被映射为新的实体。
     * @throws org.springframework.dao.DataAccessException
     *         如果从 {@link ResultSet}对象中读取数据时出错。
     */
    protected K extractKey(
            ResultSet resultSet
    ) {
        return null;
    }

    /**
     * 从 {@link ResultSet} 中映射一个实体的子元素。
     *
     * @param resultSet
     *         包含待映射数据的 {@link ResultSet} 对象。
     * @param entity
     *         已映射的实体，用于向其中添加子元素。
     * @throws org.springframework.dao.DataAccessException
     *         如果从 {@link ResultSet}对象中读取数据时出错。
     */
    protected void extractChildren(
            T entity,
            ResultSet resultSet
    ) {
    }

    @Override
    public final ResultSetExtractor<T> single() {
        final var logger = LoggerFactory.getLogger("org.xx.armory.spring5.jdbc");

        return resultSet -> {
            K key, key2;
            T entity, entity2;

            key = null;
            entity = null;
            while (resultSet.next()) {
                key2 = extractKey(resultSet);
                if (key2 == null || !Objects.equals(key2, key)) {
                    // 需要映射为新实体，终止映射，返回当前实体。
                    if (entity != null) {
                        logger.trace("Return current entity.");
                        return entity;
                    }
                    entity2 = extractEntity(resultSet);
                    if (entity2 != null) {
                        logger.trace("Read new entity, append first child if exists.");
                        key = key2;
                        entity = entity2;
                        extractChildren(entity, resultSet);
                    } else {
                        // 映射失败，直接返回null。
                        logger.trace("Cannot read entity, skip.");
                        return null;
                    }
                } else {
                    // 属于同一个实体。
                    logger.trace("Append new child to entity.");
                    extractChildren(entity, resultSet);
                }
            }

            logger.trace("Return current entity.");
            return entity;
        };
    }

    @Override
    public final ResultSetExtractor<List<T>> all() {
        final var logger = LoggerFactory.getLogger("org.xx.armory.spring5.jdbc");

        return resultSet -> {
            final var result = new ArrayList<T>();

            K key, key2;
            T entity, entity2;

            key = null;
            entity = null;
            while (resultSet.next()) {
                key2 = extractKey(resultSet);
                if (key2 == null || !Objects.equals(key2, key)) {
                    // 需要映射为新实体。
                    if (entity != null) {
                        logger.trace("Add current entity.");
                        result.add(entity);
                        entity = null;
                    }

                    entity2 = extractEntity(resultSet);
                    if (entity2 != null) {
                        logger.trace("Read new entity, append first child if exists.");
                        key = key2;
                        entity = entity2;
                        extractChildren(entity, resultSet);
                    } else {
                        // 映射失败。
                        logger.trace("Cannot read entity");
                        key = null;
                    }
                } else {
                    // 属于同一个实体。
                    logger.trace("Append new child to entity.");
                    extractChildren(entity, resultSet);
                }
            }

            if (entity != null) {
                logger.trace("Add last entity...");
                result.add(entity);
            }

            return result;
        };
    }

    @Override
    public final ResultSetExtractor<List<T>> page(
            int startRowIndex,
            int maximumRows
    ) {
        final var logger = LoggerFactory.getLogger("org.xx.armory.spring5.jdbc");

        return resultSet -> {
            // 计算一个合理的缓冲区大小。
            final var result = new ArrayList<T>(min(maximumRows, 128));

            var row = 0L;
            var more = true;
            K key = null;
            T entity = null;

            // 跳过不需要映射的记录。
            while (row < startRowIndex && (more = resultSet.next())) {
                final K key2 = extractKey(resultSet);
                if (key2 == null || !Objects.equals(key2, key)) {
                    // 需要映射为新实体。
                    logger.trace("Skip entity...");
                    key = key2;
                    ++row;
                } else {
                    // 属于同一个实体。
                }
            }

            if (more) {
                row = 0;
                key = null;
                while ((maximumRows == 0 || row < maximumRows) && resultSet.next()) {
                    final K key2 = extractKey(resultSet);
                    if (key2 == null || !Objects.equals(key2, key)) {
                        // 需要映射为新实体。
                        if (entity != null) {
                            logger.trace("Add current entity.");
                            result.add(entity);
                            entity = null;
                        }
                        final T entity2 = extractEntity(resultSet);
                        if (entity2 != null) {
                            logger.trace("Read new entity, append first child if exists.");
                            key = key2;
                            entity = entity2;
                            extractChildren(entity, resultSet);
                            ++row;
                        } else {
                            // 映射失败，不记录该实体。
                            logger.trace("Cannot read entity, skip.");
                            key = null;
                        }
                    } else {
                        // 属于同一个实体。
                        logger.trace("Append new child to entity.");
                        extractChildren(entity, resultSet);
                    }
                }

                if (entity != null) {
                    logger.trace("Add last entity...");
                    result.add(entity);
                }
            } else {
                logger.trace("No more entities found");
            }

            return result;
        };
    }
}
